/* Copyright (c) 2025 Beijing Semidrive Technology Corporation
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/** *****************************************************************************************************
 * \file     Lin_Uart.c                                                                              *
 * \brief    This file mainly implements the definition of register operation interface                 *
 *                                                                                                      *
 * <table>                                                                                              *
 * <tr><th>Date           <th>Version                                                                   *
 * <tr><td>2023/08/18     <td>1.0.0                                                                     *
 * </table>                                                                                             *
 *******************************************************************************************************/

#ifdef __cplusplus
extern "C" {
#endif

/********************************************************************************************************
 *                                      Include header files                                            *
 *******************************************************************************************************/
/* lin types header file */
#include "Lin_Types.h"
/* register operation header file */
#include "RegHelper.h"
/* lin uart register header file */
#include "Uart_Ip_Reg.h"
/* critical zone header file */
#include "SchM_Lin.h"
/* lin cfg header file */
#include "Lin_Cfg.h"

/********************************************************************************************************
 *                                         Macro definition                                             *
 *******************************************************************************************************/
/* polling time value */
#define LIN_POLLING_TIME_OUT    (1000U)

/********************************************************************************************************
 *                                      internal fcuntion                                               *
 *******************************************************************************************************/
#define LIN_START_SEC_CODE
#include "Lin_MemMap.h"

/* initialize UART module and corresponding configuration*/
void Lin_UartInit(const Lin_Uart *Lin_UartCfg)
{
    const lin_uart_cfg_t *hardwareCfg = &(Lin_UartCfg->dataPtr);
    uint32 PollTime = 0u;
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    /* PRQA S 2985 1 */
    uint32 Mod_Ctrl = readl(Lin_BaseAddr + UART_MCR0_OFF);
    /* set module operation mode */
    Mod_Ctrl &= ~FM_MCR0_OPMOD;
    Mod_Ctrl |= FV_MCR0_OPMOD(hardwareCfg->mode + 1U);
    /* PRQA S 2985 1 */
    writel(Mod_Ctrl, Lin_BaseAddr + UART_MCR0_OFF);

    /* clear all interrupt */
    writel(0xFFFFFFFF, Lin_BaseAddr + UART_INTR0_OFF);
    uint32 Pcr0 = readl(Lin_BaseAddr + UART_PCR0_OFF);
    /* set data bit: 8 bit */
    Pcr0 &= ~FM_PCR0_DATABIT;
    /* PRQA S 2985 1 */
    Pcr0 |= FV_PCR0_DATABIT(0U);
    /* set stop bit */
    Pcr0 &= ~FM_PCR0_STOPBIT;
    Pcr0 |= FV_PCR0_STOPBIT(hardwareCfg->stop);
    /* set parity */
    Pcr0 &= ~FM_PCR0_PARITYBIT;
    Pcr0 |= FV_PCR0_PARITYBIT(hardwareCfg->parity);
    Pcr0 |= BM_PCR0_TRANSFERMODE;
    writel(Pcr0, Lin_BaseAddr + UART_PCR0_OFF);

    /* set baud rate */
    uint32 Pcr1 = 0;

    uint32 Freq = hardwareCfg->frequency;
    uint32 Baud = hardwareCfg->baud_rate;
    uint32 Baudrate = Freq / Baud;
    Pcr1 = readl(Lin_BaseAddr + UART_PCR1_OFF);
    Pcr1 &= ~FM_PCR1_BAUDRATECNT;
    /* PRQA S 2985 1 */
    Pcr1 |= FV_PCR1_BAUDRATECNT(Baudrate);
    writel(Pcr1, Lin_BaseAddr + UART_PCR1_OFF);

    /* clear fifo */
    writel((uint32)BM_FCR0_CLRTXF, Lin_BaseAddr + UART_FCR0_OFF);
    writel((uint32)BM_FCR1_CLRRXF, Lin_BaseAddr + UART_FCR1_OFF);

    /* disable dma */
    // writel((BM_DMACR_TXDMAE | BM_DMACR_RXDMAE), Lin_BaseAddr + DMACR_OFF);

    /* set rx noise filter : shorter than 1 cycle */
    uint32 Pcr8 = readl(Lin_BaseAddr + UART_PCR8_OFF);
    Pcr8 &= ~FM_PCR8_RXFILTCTL;
    Pcr8 |= FV_PCR8_RXFILTCTL(3);
    Pcr8 |= BM_PCR8_RXSYNCEN;
    writel(Pcr8, Lin_BaseAddr + UART_PCR8_OFF);

    /* set break bit : 13 bit*/
    uint32 Pcr4 = readl(Lin_BaseAddr + UART_PCR4_OFF);
    Pcr4 &= ~FM_PCR4_TXBREAKCTL;
    Pcr4 |= FV_PCR4_TXBREAKCTL(LIN_UART_TXBREAK_BIT(hardwareCfg->txBreakLen));
    writel(Pcr4, Lin_BaseAddr + UART_PCR4_OFF);
    Pcr1 = readl(Lin_BaseAddr + UART_PCR1_OFF);
    Pcr1 &= ~FM_PCR1_RXBREAKCTL;
    /* master mode will set brek bit to 0 when corresponding channel wakeup */
    Pcr1 |= FV_PCR1_RXBREAKCTL(LIN_UART_RXBREAK_BIT(10));

    writel(Pcr1, Lin_BaseAddr + UART_PCR1_OFF);

    /* set frame time out value : (2 ^ 21) cycle */
    uint32 Pcr7 = 0;
    /* delay cycle: (2^20) * 12ns = 12.5ms */
    Pcr7 = FV_PCR7_PCMDTOUNIT(6);
    Pcr7 |= FV_PCR7_PCMDTOVAL(hardwareCfg->cmd_timeout);
    writel(Pcr7, Lin_BaseAddr + UART_PCR7_OFF);

    /* set lin related int enable */
    uint32 Lin_Int = 0;
    Lin_Int |= BM_INTEN0_APBCMDDONEE | BM_INTEN0_APBCMDABORTE |
               BM_INTEN0_RXPIDPASSE | BM_INTEN0_RXPIDERRE |
               BM_INTR0_STARTERR | BM_INTR0_NOISEERR |
               BM_INTR0_FRAMEERR | BM_INTR0_PARITYERR |
               BM_INTR0_RXFOVF | BM_INTR0_TXFOVF | BM_INTR0_RXFUDF | BM_INTR0_TXFUDF |
               BM_INTR0_RXBREAK;
    /* Checksum calculation of received messages by hardware */
    Lin_Int |= BM_INTEN0_RXCHKSUMPASSE | BM_INTEN0_RXCHKSUMERRE;

    writel(Lin_Int, Lin_BaseAddr + UART_INTEN0_OFF);

    uint32 Pcr13 = readl(Lin_BaseAddr + UART_PCR13_OFF);
    /* tx_frame detect: sent high check low */
    Pcr13 |= BM_PCR13_OPT_SHCL_EN;
    writel(Pcr13, Lin_BaseAddr + UART_PCR13_OFF);

    if (hardwareCfg->mode == LIN_UART_SLAVE)
    {
        /* set check sum type */
        uint32 Pcr6 = readl(Lin_BaseAddr + UART_PCR6_OFF);
        Pcr6 &= ~(0x1U << 3U);
        Pcr6 |= ((uint32)(hardwareCfg->cs_mode) << 3U);
        writel(Pcr6, Lin_BaseAddr + UART_PCR6_OFF);
    }

    Pcr0 = readl(Lin_BaseAddr + UART_PCR0_OFF);
    Pcr0 |= BM_PCR0_TXEN;
    Pcr0 |= BM_PCR0_RXEN;
    writel(Pcr0, Lin_BaseAddr + UART_PCR0_OFF);

    /* PRQA S 2985 5 */
    /* reset the module */
    Mod_Ctrl = readl(Lin_BaseAddr + UART_MCR0_OFF);
    Mod_Ctrl |= BM_MCR0_MODRST;
    writel(Mod_Ctrl, Lin_BaseAddr + UART_MCR0_OFF);
    /* Delay a while unitl setting be synced,need (4*pclk+4*24m_clk) */
    while ((readl(Lin_BaseAddr + UART_MCR0_OFF) & BM_MCR0_MODRST) && (PollTime < LIN_POLLING_TIME_OUT)){
        PollTime++;
    }
    /* PRQA S 2985 5 */
    /* enable the module */
    Mod_Ctrl = readl(Lin_BaseAddr + UART_MCR0_OFF);
    Mod_Ctrl |= BM_MCR0_MODEN;
    writel(Mod_Ctrl, Lin_BaseAddr + UART_MCR0_OFF);

    /* When using icu for auto baud rate detection, the original uart abr function does not need to be set */
    if ((hardwareCfg->mode == LIN_UART_SLAVE) && (hardwareCfg->auto_br == TRUE)) {
        /* PRQA S 2985 8 */
        /* set auto baud rate */
        Lin_Int = readl(Lin_BaseAddr + UART_INTEN0_OFF);
        Lin_Int |= BM_INTEN0_ABRPASSE | BM_INTEN0_ABRFAILE;
        writel(Lin_Int, Lin_BaseAddr + UART_INTEN0_OFF);

        Pcr0 = readl(Lin_BaseAddr + UART_PCR0_OFF);
        Pcr0 &= ~FM_PCR0_ABRCTL0;
        Pcr0 &= ~FM_PCR0_ABRCTL1;
        Pcr0 |= BM_PCR0_ABREN;
        Pcr0 |= FV_PCR0_ABRCTL0(0U);
        Pcr0 |= GFV_PCR0_ABRCTL1(0U);
        writel(Pcr0, Lin_BaseAddr + UART_PCR0_OFF);
    }
}


/**
 * @brief Sets the LIN_UART into Break state.
 *
 * Configures the specified LIN_UART to enter the Break state.
 *
 * @param Lin_UartCfg Pointer to the LIN_UART configuration structure.
 */
void Lin_Uart_Set_Break(const Lin_Uart *Lin_UartCfg)
{
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    uint32 Pcr4 = readl(Lin_BaseAddr + UART_PCR4_OFF);
    Pcr4 |= BM_PCR4_TXBREAK;
    writel(Pcr4, Lin_BaseAddr + UART_PCR4_OFF);
}


void Lin_UartClrTxFifo(const Lin_Uart *Lin_UartCfg)
{
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;

    uint32 Fcr0 = readl(Lin_BaseAddr + UART_FCR0_OFF);
    Fcr0 |= BM_FCR0_CLRTXF;
    writel(Fcr0, Lin_BaseAddr + UART_FCR0_OFF);
}

/**
 * @brief Clears the TX FIFO of the LIN UART.
 *
 * Clears the transmit FIFO of the LIN UART based on the provided configuration.
 *
 * @param Lin_UartCfg Pointer to the LIN UART configuration structure.
 */
void Lin_UartClrRxFifo(const Lin_Uart *Lin_UartCfg)
{
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;

    uint32 Fcr1 = readl(Lin_BaseAddr + UART_FCR1_OFF);
    Fcr1 |= BM_FCR1_CLRRXF;
    writel(Fcr1, Lin_BaseAddr + UART_FCR1_OFF);

}


/**
 * @brief Enables or disables the automatic baud rate detection for the LIN UART.
 *
 * Enables or disables the automatic baud rate detection feature of the LIN UART based on
 * the provided configuration and enable flag.
 *
 * @param Lin_UartCfg Pointer to the LIN UART configuration structure.
 * @param enable Boolean flag to enable (true) or disable (false) automatic baud rate detection.
 */
void Lin_UartSetAbrEn(const Lin_Uart *Lin_UartCfg, boolean enable)
{
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    uint32 Pcr0 = readl(Lin_BaseAddr + UART_PCR0_OFF);

    if (enable) {
        Pcr0 |= BM_PCR0_ABREN;
    } else {
        Pcr0 &= ~BM_PCR0_ABREN;
    }

    writel(Pcr0, Lin_BaseAddr + UART_PCR0_OFF);

}

/**
 * @brief Resets the LIN UART module.
 *
 * Resets the LIN UART module based on the provided LIN UART configuration.
 *
 * @param Lin_UartCfg Pointer to the LIN UART configuration structure.
 */
void Lin_UartRstMod(const Lin_Uart *Lin_UartCfg)
{
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    /* PRQA S 2985 10 */
    /* controller will auto detect the baud rate until module reset */
    uint32 Mod_Ctrl = readl(Lin_BaseAddr + UART_MCR0_OFF);
    Mod_Ctrl |= BM_MCR0_MODRST;
    writel(Mod_Ctrl, Lin_BaseAddr + UART_MCR0_OFF);
    uint32 PollTime = 0u;

    while (readl(Lin_BaseAddr + UART_MCR0_OFF) & BM_MCR0_MODRST) {
        /* prevent system hang */
        if (PollTime > LIN_POLLING_TIME_OUT) {
            break;
        }

        PollTime ++;
    }
}

#define LIN_STOP_SEC_CODE
#include "Lin_MemMap.h"


#define LIN_START_SEC_CODE_FAST
#include "Lin_MemMap.h"
/**
 * @brief Sets the PID value for the LIN_UART module.
 *
 * Sets the given PID value into the Command Control Status Register 1 of the LIN_UART module.
 *
 * @param Lin_UartCfg Pointer to the LIN_UART configuration structure.
 * @param Pid The PID value to be set.
 */
void Lin_UartPidSet(const Lin_Uart *Lin_UartCfg, uint8 Pid)
{
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    uint32 CmdCsr1 = readl(Lin_BaseAddr + UART_CMDCSR1_OFF);
    /* PRQA S 2985 2 */
    CmdCsr1 &= ~FM_CMDCSR1_PID;
    CmdCsr1 |= FV_CMDCSR1_PID(Pid);
    writel(CmdCsr1, Lin_BaseAddr + UART_CMDCSR1_OFF);
}

/**
 * @brief Retrieves the PID value of the LIN_UART module.
 *
 * Retrieves the PID value of the LIN_UART module based on the given LIN_UART configuration.
 *
 * @param Lin_UartCfg Pointer to the LIN_UART configuration information.
 *
 * @return Returns the PID value as a uint8 type.
 */
uint8 Lin_UartPidGet(const Lin_Uart *Lin_UartCfg)
{
    uint8 Pid = 0;
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    uint32 Psr4 = readl(Lin_BaseAddr + UART_PSR4_OFF);
    Pid = (uint8)(Psr4 & FM_PSR4_LINPID);
    return Pid;
}

/**
 * @brief Transmit data via the LIN UART.
 *
 * Sends the specified data through the UART using the given LIN UART configuration.
 *
 * @param Lin_UartCfg Pointer to the LIN UART configuration structure.
 * @param cs Checksum type to be used.
 * @param point2data Pointer to the data buffer to be transmitted.
 * @param numberBytes Number of bytes to be transmitted.
 */
void Lin_UartTransmit(const Lin_Uart *Lin_UartCfg, uint8 cs, uint8 *point2data, uint8 numberBytes)
{
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    uint8 i = 0U;
    uint32 Cmdcsr1 = readl(Lin_BaseAddr + UART_CMDCSR1_OFF);
    /* PRQA S 2985 10 */
    Cmdcsr1 &= ~FM_CMDCSR1_TXBYTENUM;
    Cmdcsr1 |= FV_CMDCSR1_TXBYTENUM(numberBytes);
    Cmdcsr1 &= ~FM_CMDCSR1_RXBYTENUM;
    Cmdcsr1 |= FV_CMDCSR1_RXBYTENUM(0U);
    writel(Cmdcsr1, Lin_BaseAddr + UART_CMDCSR1_OFF);
    uint32 Mcr0 = readl(Lin_BaseAddr + UART_MCR0_OFF);

    /* opmod=1 : master mode */
    if (GFV_MCR0_OPMOD(Mcr0) == 1) {
        /* set check sum type */
        uint32 Pcr6 = readl(Lin_BaseAddr + UART_PCR6_OFF);
        Pcr6 &= ~BM_PCR6_CHKSUMTYP;
        Pcr6 |= ((uint32)cs << 3U);
        writel(Pcr6, Lin_BaseAddr + UART_PCR6_OFF);
    }

    /* clr tx fifo */
    uint32 Fcr0 = readl(Lin_BaseAddr + UART_FCR0_OFF);
    Fcr0 |= BM_FCR0_CLRTXF;
    writel(Fcr0, Lin_BaseAddr + UART_FCR0_OFF);
    /* Guarantees write fifo and doorbell continuity */
    SchM_Enter_Lin_LIN_EXCLUSIVE_AREA_00();
    uint32 Cmdcsr0 = FV_CMDCSR0_DOORBELL(0xA53C);
    writel(Cmdcsr0, Lin_BaseAddr + UART_CMDCSR0_OFF);

    while (i < numberBytes) {
        writel(*point2data, Lin_BaseAddr + UART_TXDR_OFF);
        i++;
        point2data++;
    }

    SchM_Exit_Lin_LIN_EXCLUSIVE_AREA_00();
}

/**
 * @brief Receive data via the LIN UART.
 *
 * Receives data through the LIN UART based on the given configuration, checksum type, and number of bytes.
 *
 * @param Lin_UartCfg Pointer to the LIN UART configuration.
 * @param cs Checksum type to be used for validation.
 * @param numberBytes Number of bytes expected to be received.
 */
void Lin_UartReceive(const Lin_Uart *Lin_UartCfg, uint8 cs, uint8 numberBytes)
{
    /* Guarantees write reg and rx doorbell continuity */
    SchM_Enter_Lin_LIN_EXCLUSIVE_AREA_01();
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    uint32 Cmdcsr1 = readl(Lin_BaseAddr + UART_CMDCSR1_OFF);
    /* PRQA S 2985 10 */
    Cmdcsr1 &= ~FM_CMDCSR1_TXBYTENUM;
    Cmdcsr1 |= FV_CMDCSR1_TXBYTENUM(0U);
    Cmdcsr1 &= ~FM_CMDCSR1_RXBYTENUM;
    Cmdcsr1 |= FV_CMDCSR1_RXBYTENUM(numberBytes);
    writel(Cmdcsr1, Lin_BaseAddr + UART_CMDCSR1_OFF);

    uint32 Mcr0 = readl(Lin_BaseAddr + UART_MCR0_OFF);

    /* opmod=1 : master mode */
    if ((Mcr0 & FM_MCR0_OPMOD) == (0x1 << 4)) {
        /* set check sum type */
        uint32 Pcr6 = readl(Lin_BaseAddr + UART_PCR6_OFF);
        Pcr6 &= ~BM_PCR6_CHKSUMTYP;
        Pcr6 |= ((uint32)cs << 3U);
        writel(Pcr6, Lin_BaseAddr + UART_PCR6_OFF);
    }

    uint32 Cmdcsr0 = FV_CMDCSR0_DOORBELL(0xA53C);
    writel(Cmdcsr0, Lin_BaseAddr + UART_CMDCSR0_OFF);
    SchM_Exit_Lin_LIN_EXCLUSIVE_AREA_01();
}

/**
 * @brief Reads a specified number of characters from the LIN UART.
 *
 * Reads a specified number of characters from the LIN UART based on the given configuration
 * and stores them in the provided data buffer.
 *
 * @param Lin_UartCfg Pointer to the LIN UART configuration.
 * @param data Pointer to the data buffer where the read characters will be stored.
 * @param numberBytes Number of characters to read.
 */
void Lin_UartGetChar(const Lin_Uart *Lin_UartCfg, uint8 *data, uint8 numberBytes)
{
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    uint32 Fifo_Sta = readl(Lin_BaseAddr + UART_FSR1_OFF);
    /* PRQA S 2985 2 */
    uint32 Rx_Byte_Num = GFV_FSR1_FILLLVL(Fifo_Sta);
    if (Rx_Byte_Num < numberBytes) {
        /* make sure the fifo data is not read out of bounds */
        uint32 Fcr1 = readl(Lin_BaseAddr + UART_FCR1_OFF);
        Fcr1 |= BM_FCR1_CLRRXF;
        /* clr rx fifo */
        writel(Fcr1, Lin_BaseAddr + UART_FCR1_OFF);
    } else {
        for (uint8 i = 0; i < numberBytes; i++) {
            data[i] = (uint8)readl(Lin_BaseAddr + UART_RXDR_OFF);
        }
    }
}

/**
 * @brief Gets the LIN UART interrupt status.
 *
 * Retrieves the interrupt status from the specified LIN UART configuration.
 *
 * @param Lin_UartCfg Pointer to the LIN UART configuration.
 *
 * @return Returns the LIN UART interrupt status.
 */
uint32 Lin_UartGetInt(const Lin_Uart *Lin_UartCfg)
{
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    uint32 Lin_Int = readl(Lin_BaseAddr + UART_INTR0_OFF);
    return Lin_Int;
}

/**
 * @brief Clears LIN UART interrupts.
 *
 * Clears the LIN UART interrupts based on the given LIN UART configuration and state.
 *
 * @param Lin_UartCfg Pointer to the LIN UART configuration.
 * @param state Interrupt state to clear.
 */
void Lin_UartClearInt(const Lin_Uart *Lin_UartCfg, uint32 state)
{
    uint32 Lin_BaseAddr= Lin_UartCfg->linModuleBase;
    writel(state, Lin_BaseAddr + UART_INTR0_OFF);
}

#define LIN_STOP_SEC_CODE_FAST
#include "Lin_MemMap.h"

#ifdef __cplusplus
}
#endif
